{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 02. PyTorch Hello World - 构建第一个神经网络\n",
        "\n",
        "## 学习目标\n",
        "- 学习PyTorch的高级API\n",
        "- 构建第一个简单的神经网络\n",
        "- 理解训练循环的基本结构\n",
        "- 掌握数据加载和预处理\n",
        "- 学习模型保存和加载\n",
        "\n",
        "## 什么是神经网络？\n",
        "\n",
        "神经网络是由多个层组成的计算模型，每一层都包含多个神经元（节点）。每个神经元接收输入，进行加权求和，然后通过激活函数产生输出。\n",
        "\n",
        "在这个教程中，我们将构建一个简单的多层感知机（MLP）来解决一个分类问题。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import torch\n",
        "import torch.nn as nn\n",
        "import torch.optim as optim\n",
        "import torch.nn.functional as F\n",
        "from torch.utils.data import DataLoader, TensorDataset\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from sklearn.datasets import make_classification\n",
        "from sklearn.model_selection import train_test_split\n",
        "from sklearn.preprocessing import StandardScaler\n",
        "\n",
        "# 设置中文字体\n",
        "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
        "plt.rcParams['axes.unicode_minus'] = False\n",
        "\n",
        "# 设置随机种子\n",
        "torch.manual_seed(42)\n",
        "np.random.seed(42)\n",
        "\n",
        "print(f\"PyTorch版本: {torch.__version__}\")\n",
        "print(f\"CUDA可用: {torch.cuda.is_available()}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. 数据准备\n",
        "\n",
        "首先，我们创建一个简单的二分类数据集。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 生成分类数据\n",
        "X, y = make_classification(\n",
        "    n_samples=1000,\n",
        "    n_features=2,\n",
        "    n_redundant=0,\n",
        "    n_informative=2,\n",
        "    n_clusters_per_class=1,\n",
        "    random_state=42\n",
        ")\n",
        "\n",
        "# 数据分割\n",
        "X_train, X_test, y_train, y_test = train_test_split(\n",
        "    X, y, test_size=0.2, random_state=42\n",
        ")\n",
        "\n",
        "# 数据标准化\n",
        "scaler = StandardScaler()\n",
        "X_train = scaler.fit_transform(X_train)\n",
        "X_test = scaler.transform(X_test)\n",
        "\n",
        "# 转换为PyTorch张量\n",
        "X_train = torch.FloatTensor(X_train)\n",
        "X_test = torch.FloatTensor(X_test)\n",
        "y_train = torch.LongTensor(y_train)\n",
        "y_test = torch.LongTensor(y_test)\n",
        "\n",
        "print(f\"训练集大小: {X_train.shape}\")\n",
        "print(f\"测试集大小: {X_test.shape}\")\n",
        "print(f\"类别分布: {np.bincount(y_train)}\")\n",
        "\n",
        "# 可视化数据\n",
        "plt.figure(figsize=(8, 6))\n",
        "plt.scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1], \n",
        "           c='red', label='类别 0', alpha=0.7)\n",
        "plt.scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1], \n",
        "           c='blue', label='类别 1', alpha=0.7)\n",
        "plt.xlabel('特征 1')\n",
        "plt.ylabel('特征 2')\n",
        "plt.title('训练数据分布')\n",
        "plt.legend()\n",
        "plt.grid(True)\n",
        "plt.show()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. 定义神经网络模型\n",
        "\n",
        "现在我们来定义我们的第一个神经网络。我们将使用PyTorch的`nn.Module`类。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "class SimpleNN(nn.Module):\n",
        "    \"\"\"\n",
        "    简单的多层感知机\n",
        "    结构: 输入层(2) -> 隐藏层(10) -> 隐藏层(5) -> 输出层(2)\n",
        "    \"\"\"\n",
        "    def __init__(self, input_size=2, hidden_size1=10, hidden_size2=5, output_size=2):\n",
        "        super(SimpleNN, self).__init__()\n",
        "        \n",
        "        # 定义网络层\n",
        "        self.fc1 = nn.Linear(input_size, hidden_size1)  # 第一层\n",
        "        self.fc2 = nn.Linear(hidden_size1, hidden_size2)  # 第二层\n",
        "        self.fc3 = nn.Linear(hidden_size2, output_size)  # 输出层\n",
        "        \n",
        "        # 激活函数\n",
        "        self.relu = nn.ReLU()\n",
        "        self.dropout = nn.Dropout(0.2)\n",
        "        \n",
        "    def forward(self, x):\n",
        "        \"\"\"\n",
        "        前向传播\n",
        "        \"\"\"\n",
        "        # 第一层: 线性变换 + 激活函数\n",
        "        x = self.fc1(x)\n",
        "        x = self.relu(x)\n",
        "        x = self.dropout(x)\n",
        "        \n",
        "        # 第二层: 线性变换 + 激活函数\n",
        "        x = self.fc2(x)\n",
        "        x = self.relu(x)\n",
        "        x = self.dropout(x)\n",
        "        \n",
        "        # 输出层: 线性变换（不需要激活函数，因为使用CrossEntropyLoss）\n",
        "        x = self.fc3(x)\n",
        "        \n",
        "        return x\n",
        "\n",
        "# 创建模型实例\n",
        "model = SimpleNN()\n",
        "print(\"模型结构:\")\n",
        "print(model)\n",
        "\n",
        "# 计算模型参数数量\n",
        "total_params = sum(p.numel() for p in model.parameters())\n",
        "trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
        "print(f\"\\n总参数数量: {total_params}\")\n",
        "print(f\"可训练参数数量: {trainable_params}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. 定义损失函数和优化器\n",
        "\n",
        "接下来，我们需要定义损失函数和优化器。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 损失函数：交叉熵损失（适用于多分类问题）\n",
        "criterion = nn.CrossEntropyLoss()\n",
        "\n",
        "# 优化器：Adam优化器\n",
        "optimizer = optim.Adam(model.parameters(), lr=0.001)\n",
        "\n",
        "# 学习率调度器（可选）\n",
        "scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.9)\n",
        "\n",
        "print(f\"损失函数: {criterion}\")\n",
        "print(f\"优化器: {optimizer}\")\n",
        "print(f\"初始学习率: {optimizer.param_groups[0]['lr']}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 4. 创建数据加载器\n",
        "\n",
        "PyTorch的DataLoader可以帮助我们批量处理数据。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建数据集\n",
        "train_dataset = TensorDataset(X_train, y_train)\n",
        "test_dataset = TensorDataset(X_test, y_test)\n",
        "\n",
        "# 创建数据加载器\n",
        "batch_size = 32\n",
        "train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
        "test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n",
        "\n",
        "print(f\"训练批次数: {len(train_loader)}\")\n",
        "print(f\"测试批次数: {len(test_loader)}\")\n",
        "\n",
        "# 查看一个批次的数据\n",
        "for batch_x, batch_y in train_loader:\n",
        "    print(f\"批次输入形状: {batch_x.shape}\")\n",
        "    print(f\"批次标签形状: {batch_y.shape}\")\n",
        "    print(f\"批次标签: {batch_y}\")\n",
        "    break\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 5. 训练循环\n",
        "\n",
        "现在我们来编写训练循环，这是深度学习的核心部分。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def train_model(model, train_loader, criterion, optimizer, num_epochs=100):\n",
        "    \"\"\"\n",
        "    训练模型\n",
        "    \"\"\"\n",
        "    model.train()  # 设置为训练模式\n",
        "    train_losses = []\n",
        "    train_accuracies = []\n",
        "    \n",
        "    for epoch in range(num_epochs):\n",
        "        running_loss = 0.0\n",
        "        correct = 0\n",
        "        total = 0\n",
        "        \n",
        "        for batch_idx, (data, target) in enumerate(train_loader):\n",
        "            # 清零梯度\n",
        "            optimizer.zero_grad()\n",
        "            \n",
        "            # 前向传播\n",
        "            outputs = model(data)\n",
        "            loss = criterion(outputs, target)\n",
        "            \n",
        "            # 反向传播\n",
        "            loss.backward()\n",
        "            \n",
        "            # 更新参数\n",
        "            optimizer.step()\n",
        "            \n",
        "            # 统计\n",
        "            running_loss += loss.item()\n",
        "            _, predicted = torch.max(outputs.data, 1)\n",
        "            total += target.size(0)\n",
        "            correct += (predicted == target).sum().item()\n",
        "        \n",
        "        # 计算平均损失和准确率\n",
        "        epoch_loss = running_loss / len(train_loader)\n",
        "        epoch_acc = 100 * correct / total\n",
        "        \n",
        "        train_losses.append(epoch_loss)\n",
        "        train_accuracies.append(epoch_acc)\n",
        "        \n",
        "        # 更新学习率\n",
        "        scheduler.step()\n",
        "        \n",
        "        # 打印进度\n",
        "        if epoch % 20 == 0:\n",
        "            print(f'Epoch [{epoch}/{num_epochs}], '\n",
        "                  f'Loss: {epoch_loss:.4f}, '\n",
        "                  f'Accuracy: {epoch_acc:.2f}%, '\n",
        "                  f'LR: {optimizer.param_groups[0][\"lr\"]:.6f}')\n",
        "    \n",
        "    return train_losses, train_accuracies\n",
        "\n",
        "# 开始训练\n",
        "print(\"开始训练...\")\n",
        "train_losses, train_accuracies = train_model(\n",
        "    model, train_loader, criterion, optimizer, num_epochs=100\n",
        ")\n",
        "print(\"训练完成！\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 6. 模型评估\n",
        "\n",
        "训练完成后，我们需要评估模型在测试集上的性能。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def evaluate_model(model, test_loader, criterion):\n",
        "    \"\"\"\n",
        "    评估模型\n",
        "    \"\"\"\n",
        "    model.eval()  # 设置为评估模式\n",
        "    test_loss = 0.0\n",
        "    correct = 0\n",
        "    total = 0\n",
        "    all_predictions = []\n",
        "    all_targets = []\n",
        "    \n",
        "    with torch.no_grad():  # 禁用梯度计算\n",
        "        for data, target in test_loader:\n",
        "            outputs = model(data)\n",
        "            loss = criterion(outputs, target)\n",
        "            \n",
        "            test_loss += loss.item()\n",
        "            _, predicted = torch.max(outputs.data, 1)\n",
        "            total += target.size(0)\n",
        "            correct += (predicted == target).sum().item()\n",
        "            \n",
        "            all_predictions.extend(predicted.cpu().numpy())\n",
        "            all_targets.extend(target.cpu().numpy())\n",
        "    \n",
        "    test_loss /= len(test_loader)\n",
        "    accuracy = 100 * correct / total\n",
        "    \n",
        "    print(f'测试集结果:')\n",
        "    print(f'平均损失: {test_loss:.4f}')\n",
        "    print(f'准确率: {accuracy:.2f}%')\n",
        "    print(f'正确预测: {correct}/{total}')\n",
        "    \n",
        "    return test_loss, accuracy, all_predictions, all_targets\n",
        "\n",
        "# 评估模型\n",
        "test_loss, test_accuracy, predictions, targets = evaluate_model(\n",
        "    model, test_loader, criterion\n",
        ")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 7. 可视化结果\n",
        "\n",
        "让我们可视化训练过程和模型性能。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 可视化训练过程\n",
        "plt.figure(figsize=(15, 5))\n",
        "\n",
        "# 损失曲线\n",
        "plt.subplot(1, 3, 1)\n",
        "plt.plot(train_losses)\n",
        "plt.title('训练损失')\n",
        "plt.xlabel('Epoch')\n",
        "plt.ylabel('Loss')\n",
        "plt.grid(True)\n",
        "\n",
        "# 准确率曲线\n",
        "plt.subplot(1, 3, 2)\n",
        "plt.plot(train_accuracies)\n",
        "plt.title('训练准确率')\n",
        "plt.xlabel('Epoch')\n",
        "plt.ylabel('Accuracy (%)')\n",
        "plt.grid(True)\n",
        "\n",
        "# 决策边界可视化\n",
        "plt.subplot(1, 3, 3)\n",
        "h = 0.02\n",
        "x_min, x_max = X_test[:, 0].min() - 1, X_test[:, 0].max() + 1\n",
        "y_min, y_max = X_test[:, 1].min() - 1, X_test[:, 1].max() + 1\n",
        "xx, yy = np.meshgrid(np.arange(x_min, x_max, h),\n",
        "                     np.arange(y_min, y_max, h))\n",
        "\n",
        "# 预测网格点\n",
        "model.eval()\n",
        "with torch.no_grad():\n",
        "    Z = model(torch.FloatTensor(np.c_[xx.ravel(), yy.ravel()]))\n",
        "    Z = torch.max(Z, 1)[1].numpy()\n",
        "    Z = Z.reshape(xx.shape)\n",
        "\n",
        "plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdYlBu)\n",
        "plt.scatter(X_test[targets == 0, 0], X_test[targets == 0, 1], \n",
        "           c='red', label='类别 0', alpha=0.7)\n",
        "plt.scatter(X_test[targets == 1, 0], X_test[targets == 1, 1], \n",
        "           c='blue', label='类别 1', alpha=0.7)\n",
        "plt.title('决策边界')\n",
        "plt.xlabel('特征 1')\n",
        "plt.ylabel('特征 2')\n",
        "plt.legend()\n",
        "plt.grid(True)\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 8. 模型保存和加载\n",
        "\n",
        "学会如何保存和加载模型是很重要的。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 保存模型\n",
        "model_path = 'simple_nn_model.pth'\n",
        "torch.save({\n",
        "    'model_state_dict': model.state_dict(),\n",
        "    'optimizer_state_dict': optimizer.state_dict(),\n",
        "    'train_losses': train_losses,\n",
        "    'train_accuracies': train_accuracies,\n",
        "    'test_accuracy': test_accuracy\n",
        "}, model_path)\n",
        "\n",
        "print(f\"模型已保存到: {model_path}\")\n",
        "\n",
        "# 加载模型\n",
        "checkpoint = torch.load(model_path)\n",
        "new_model = SimpleNN()\n",
        "new_model.load_state_dict(checkpoint['model_state_dict'])\n",
        "\n",
        "# 验证加载的模型\n",
        "new_model.eval()\n",
        "with torch.no_grad():\n",
        "    test_output = new_model(X_test[:5])\n",
        "    print(f\"加载的模型前5个测试样本预测: {torch.max(test_output, 1)[1].numpy()}\")\n",
        "    print(f\"真实标签: {y_test[:5].numpy()}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 9. 练习题目\n",
        "\n",
        "### 练习1：修改网络结构\n",
        "尝试修改网络结构，增加或减少隐藏层，观察对性能的影响。\n",
        "\n",
        "### 练习2：调整超参数\n",
        "尝试不同的学习率、批次大小、优化器，找到最佳配置。\n",
        "\n",
        "### 练习3：添加正则化\n",
        "在模型中添加L2正则化（权重衰减），观察对过拟合的影响。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 练习1：修改网络结构 - 更深的网络\n",
        "class DeeperNN(nn.Module):\n",
        "    def __init__(self, input_size=2, output_size=2):\n",
        "        super(DeeperNN, self).__init__()\n",
        "        self.fc1 = nn.Linear(input_size, 20)\n",
        "        self.fc2 = nn.Linear(20, 15)\n",
        "        self.fc3 = nn.Linear(15, 10)\n",
        "        self.fc4 = nn.Linear(10, 5)\n",
        "        self.fc5 = nn.Linear(5, output_size)\n",
        "        self.relu = nn.ReLU()\n",
        "        self.dropout = nn.Dropout(0.3)\n",
        "        \n",
        "    def forward(self, x):\n",
        "        x = self.relu(self.fc1(x))\n",
        "        x = self.dropout(x)\n",
        "        x = self.relu(self.fc2(x))\n",
        "        x = self.dropout(x)\n",
        "        x = self.relu(self.fc3(x))\n",
        "        x = self.dropout(x)\n",
        "        x = self.relu(self.fc4(x))\n",
        "        x = self.dropout(x)\n",
        "        x = self.fc5(x)\n",
        "        return x\n",
        "\n",
        "# 创建更深的模型\n",
        "deeper_model = DeeperNN()\n",
        "deeper_optimizer = optim.Adam(deeper_model.parameters(), lr=0.001, weight_decay=1e-4)\n",
        "\n",
        "print(\"更深的网络结构:\")\n",
        "print(deeper_model)\n",
        "\n",
        "# 快速训练测试\n",
        "print(\"\\n训练更深的网络...\")\n",
        "deeper_losses, deeper_accuracies = train_model(\n",
        "    deeper_model, train_loader, criterion, deeper_optimizer, num_epochs=50\n",
        ")\n",
        "\n",
        "# 比较性能\n",
        "print(f\"\\n原始模型测试准确率: {test_accuracy:.2f}%\")\n",
        "deeper_test_loss, deeper_test_accuracy, _, _ = evaluate_model(\n",
        "    deeper_model, test_loader, criterion\n",
        ")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 练习2：调整超参数\n",
        "print(\"练习2：调整超参数\")\n",
        "print(\"-\" * 40)\n",
        "\n",
        "# 测试不同学习率\n",
        "learning_rates = [0.001, 0.01, 0.1]\n",
        "results = []\n",
        "\n",
        "for lr in learning_rates:\n",
        "    print(f\"\\n测试学习率: {lr}\")\n",
        "    \n",
        "    # 创建模型\n",
        "    model = SimpleNN()\n",
        "    criterion = nn.CrossEntropyLoss()\n",
        "    optimizer = optim.Adam(model.parameters(), lr=lr)\n",
        "    \n",
        "    # 训练\n",
        "    trainer = ModelTrainer(model, criterion, optimizer)\n",
        "    train_time = trainer.train(train_loader, test_loader, num_epochs=30, verbose=False)\n",
        "    \n",
        "    # 评估\n",
        "    test_loss, test_accuracy, _, _ = trainer.evaluate(test_loader)\n",
        "    \n",
        "    results.append({\n",
        "        'lr': lr,\n",
        "        'accuracy': test_accuracy,\n",
        "        'time': train_time\n",
        "    })\n",
        "    \n",
        "    print(f\"  准确率: {test_accuracy:.2f}%\")\n",
        "    print(f\"  训练时间: {train_time:.2f}秒\")\n",
        "\n",
        "# 找出最佳学习率\n",
        "best_lr = max(results, key=lambda x: x['accuracy'])\n",
        "print(f\"\\n最佳学习率: {best_lr['lr']} (准确率: {best_lr['accuracy']:.2f}%)\")\n"
      ]
    }
  ],
  "metadata": {
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
